<template>
  <view class="virtual-list-container">
    <scroll-view
      :scroll-y="true"
      :style="{ height: height + 'px' }"
      class="virtual-list"
      @scroll="handleScroll"
      :enhanced="true"
      :show-scrollbar="showScrollbar"
      :bounces="bounces"
    >
      <view class="virtual-list-phantom" :style="{ height: listHeight + 'px' }">
        <view
          class="virtual-list-content"
          :style="{ transform: `translate3d(0, ${offset}px, 0)` }"
        >
          <view
            v-for="item in visibleData"
            :key="item.index"
            :style="{ height: itemSize + 'px' }"
            class="virtual-list-item"
          >
            <slot :item="item.data" :index="item.index" name="item"></slot>
          </view>
        </view>
      </view>
      <view v-if="loading" class="loading-more">
        <text>加载中...</text>
      </view>
      <view v-if="!hasMore && !loading" class="no-more">
        <text>没有更多数据了</text>
      </view>
    </scroll-view>
  </view>
</template>

<script>
const OVERSCAN_SIZE = 5; // 上下额外渲染的项数

export default {
  name: "VirtualList",

  props: {
    // 列表高度
    height: {
      type: Number,
      required: true,
    },
    // 列表数据
    listData: {
      type: Array,
      default: () => [],
    },
    // 每项高度
    itemSize: {
      type: Number,
      default: 60,
    },
    // 是否显示滚动条
    showScrollbar: {
      type: Boolean,
      default: true,
    },
    // 是否有弹性
    bounces: {
      type: Boolean,
      default: true,
    },
    // 是否还有更多数据
    hasMore: {
      type: Boolean,
      default: true,
    },
    // 加载状态
    loading: {
      type: Boolean,
      default: false,
    },
    // 触发加载的距离
    loadingOffset: {
      type: Number,
      default: 50,
    },
  },

  data() {
    return {
      offset: 0,
      start: 0,
      end: 0,
      scrollTop: 0,
      lastScrollTop: 0,
      scrollDirection: "forward",
      timer: null,
    };
  },

  computed: {
    // 列表总高度
    listHeight() {
      return this.listData.length * this.itemSize;
    },

    // 可视区域能显示的项数
    visibleCount() {
      return Math.ceil(this.height / this.itemSize);
    },

    // 当前可视区域的数据
    visibleData() {
      const start = Math.max(0, this.start - OVERSCAN_SIZE);
      const end = Math.min(this.listData.length, this.end + OVERSCAN_SIZE);

      return this.listData.slice(start, end).map((item, index) => ({
        data: item,
        index: start + index,
      }));
    },
  },

  watch: {
    listData() {
      this.updateVisibleData();
    },
  },

  mounted() {
    this.updateVisibleData();
  },

  beforeUnmount() {
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  },

  methods: {
    // 处理滚动事件
    handleScroll(event) {
      if (this.timer) {
        clearTimeout(this.timer);
      }

      this.timer = setTimeout(() => {
        const { scrollTop } = event.detail;

        // 判断滚动方向
        this.scrollDirection =
          scrollTop > this.lastScrollTop ? "forward" : "backward";
        this.lastScrollTop = scrollTop;

        // 更新可视区域数据
        this.scrollTop = scrollTop;
        this.updateVisibleData();

        // 触发加载更多
        if (
          this.scrollDirection === "forward" &&
          !this.loading &&
          this.hasMore &&
          this.listHeight - scrollTop - this.height <= this.loadingOffset
        ) {
          this.$emit("loadMore");
        }

        // 触发滚动事件
        this.$emit("scroll", {
          scrollTop,
          scrollDirection: this.scrollDirection,
          isBottom:
            this.listHeight - scrollTop - this.height <= this.loadingOffset,
        });
      }, 16); // 16ms = 1帧
    },

    // 更新可视区域数据
    updateVisibleData() {
      this.start = Math.floor(this.scrollTop / this.itemSize);
      this.end = this.start + this.visibleCount;
      this.offset = this.start * this.itemSize;
    },

    // 滚动到指定位置
    scrollTo(index) {
      const scrollTop = index * this.itemSize;
      this.scrollTop = scrollTop;
      this.updateVisibleData();
    },

    // 重置列表
    reset() {
      this.scrollTop = 0;
      this.lastScrollTop = 0;
      this.scrollDirection = "forward";
      this.updateVisibleData();
    },
  },
};
</script>

<style lang="scss">
.virtual-list-container {
  position: relative;
  overflow: hidden;

  .virtual-list {
    position: relative;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;

    .virtual-list-phantom {
      position: relative;
      width: 100%;
    }

    .virtual-list-content {
      position: absolute;
      left: 0;
      right: 0;
      top: 0;
      will-change: transform;
    }

    .virtual-list-item {
      width: 100%;
      box-sizing: border-box;
    }
  }

  .loading-more,
  .no-more {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20rpx 0;
    color: #000;
    font-size: var(--nut-font-size-1);

    .nut-icon {
      margin-right: 10rpx;
    }
  }
}

// 深色模式
[data-theme="dark"] {
  .virtual-list-container {
    .loading-more,
    .no-more {
      color: #000;
    }
  }
}
</style>
